package com.thd.springboot.framework.cloud.feign.log;

import com.thd.springboot.framework.utils.JacksonUtil;
import com.thd.springboot.framework.web.utils.WebUtils;
import feign.Request;
import feign.Response;
import feign.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.StringTokenizer;

import static feign.Util.*;

/**
 * feign日志配置
 *
 * @author LiLong
 * @date: 2019-07-09
 */
public class InfoFeignLogger extends feign.Logger {

    private final Logger logger;
    // private static final ThreadLocal<String> logIdLocal = new ThreadLocal<>();
    private static final ThreadLocal<String[]> logLocal = new ThreadLocal<>();
    @Autowired
    private FeignLoggingListener feignLoggingListener;

    public InfoFeignLogger() {
        this.logger = LoggerFactory.getLogger(InfoFeignLogger.class);
    }

    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
        if (logger.isInfoEnabled()) {
            // super.logRequest(configKey, logLevel, request);
            String url = request.url();
            log(configKey, "---> %s %s HTTP/1.1", request.httpMethod().name(), url);
            if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
                for (String field : request.headers().keySet()) {
                    for (String value : valuesOrEmpty(request.headers(), field)) {
                        log(configKey, "%s: %s", field, value);
                    }
                }
                int bodyLength = 0;
                String bodyText = null;
                if (request.body() != null) {
                    bodyLength = request.body().length;
                    if (logLevel.ordinal() >= Level.FULL.ordinal()) {
                        bodyText = request.charset() != null ? new String(request.body(), request.charset()) : null;
                        log(configKey, ""); // CRLF
                        log(configKey, "param: %s", bodyText != null ? bodyText : "Binary data");
                    }
                } else {
                    StringTokenizer urls = new StringTokenizer(url, "?");
                    if (urls.hasMoreTokens()) url = urls.nextToken();
                    if (urls.hasMoreTokens())
                        bodyText = JacksonUtil.objToJson(WebUtils.getParameterMap(urls.nextToken(), "UTF-8"));

                }
                log(configKey, "---> END HTTP (%s-byte body)", bodyLength);
                // String logId = feignLoggingListener.recordLog(null, url, bodyText, configKey);
                // logIdLocal.set(logId);
                logLocal.set(new String[]{url, bodyText});
            }
        }
    }

    @Override
    protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
        if (logger.isInfoEnabled()) {
            // return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
            String reason = response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ? " " + response.reason() : "";
            int status = response.status();
            log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
            if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
                for (String field : response.headers().keySet()) {
                    for (String value : valuesOrEmpty(response.headers(), field)) {
                        log(configKey, "%s: %s", field, value);
                    }
                }
                int bodyLength = 0;
                String binary_data = null;
                String[] reqLog = logLocal.get();
                if (response.body() != null && !(status == 204 || status == 205)) {
                    if (logLevel.ordinal() >= Level.FULL.ordinal()) {
                        log(configKey, ""); // CRLF
                    }
                    byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                    bodyLength = bodyData.length;
                    if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) {
                        binary_data = decodeOrDefault(bodyData, UTF_8, "Binary data");
                        log(configKey, "return: %s", binary_data);
                    }
                    log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
                    // feignLoggingListener.updateLog(logIdLocal.get(), "S", elapsedTime, binary_data);
                    feignLoggingListener.recordLog(null, reqLog[0], reqLog[1], configKey, "S", elapsedTime, binary_data);
                    return response.toBuilder().body(bodyData).build();
                } else {
                    log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
                    // feignLoggingListener.updateLog(logIdLocal.get(), "F", elapsedTime, reason);
                    feignLoggingListener.recordLog(null, reqLog[0], reqLog[1], configKey, "F", elapsedTime, reason);
                }
                logLocal.remove();
            }
            return response;
        }
        return response;
    }

    @Override
    protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
        log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(), elapsedTime);
        if (logLevel.ordinal() >= Level.FULL.ordinal()) {
            StringWriter sw = new StringWriter();
            ioe.printStackTrace(new PrintWriter(sw));
            log(configKey, "%s", sw.toString());
            log(configKey, "<--- END ERROR");
            // feignLoggingListener.updateLog(logIdLocal.get(), "F", elapsedTime, ioe.getMessage());
            String[] reqLog = logLocal.get();
            feignLoggingListener.recordLog(null, reqLog[0], reqLog[1], configKey, "F", elapsedTime, ioe.getMessage());
            logLocal.remove();
        }
        return ioe;
    }

    @Override
    protected void log(String configKey, String format, Object... args) {
        if (logger.isInfoEnabled()) {
            logger.info(String.format(methodTag(configKey) + format, args));
        }
    }
}
